﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using TicTacGadget.Services;
using System.Threading;
using System.Windows.Media.Animation;

namespace TicTacGadget
{
    public partial class MainPage : UserControl, IGameHost
    {
        DuplexServiceClient client;

        GameBoard CurrentGame()
        {
            return GameArea.Children.OfType<GameBoard>().FirstOrDefault();
        }

        IEnumerable<GameBoard> PreviousGames()
        {
            return Cementary.Children.OfType<GameBoard>();
        }

        DateTime lastMove = DateTime.Now;
        Timer timer;
        TimeSpan inactivityTimeout = TimeSpan.FromMinutes(2);

        public MainPage(string serviceUrl, string player)
        {
            Player = player;
            HostId = Guid.NewGuid();
            Debug.WriteLine("Firing up player " + player + " on host " + HostId);

            InitializeComponent();

            CustomBinding binding = new CustomBinding(
                new PollingDuplexBindingElement() { ClientPollTimeout = TimeSpan.FromSeconds(5), InactivityTimeout = TimeSpan.FromHours(1) },
                new BinaryMessageEncodingBindingElement(),
                new HttpTransportBindingElement());

            client = new DuplexServiceClient(binding, new EndpointAddress(serviceUrl));
            client.SendToClientReceived += OnReceivedMessageFromServer;
            client.InnerChannel.Faulted += delegate(object sender, EventArgs e) { Debug.WriteLine("InnerChannel_Faulted faulted"); };
            client.InnerChannel.Closing += delegate(object sender, EventArgs e) { Debug.WriteLine("InnerChannel_Closing closing"); };

            NewGame(Guid.NewGuid());

            client.SendToServiceAsync(new InquiryMessage { RequestedBy = HostId });

            timer = new Timer(delegate { Dispatcher.BeginInvoke(timer_Timeout); }, null, (int)inactivityTimeout.TotalMilliseconds / 10, (int)inactivityTimeout.TotalMilliseconds / 10);
        }

        // event handlers

        void OnReceivedMessageFromServer(object sender, SendToClientReceivedEventArgs e)
        {
            Debug.WriteLine("OnReceivedMessageFromServer: " + e.msg);

            if (e.Error != null || e.Cancelled)
            {
                Debug.WriteLine("Error: " + e.Error);
                return;
            }

            CallMessageHandler(OnInquiryMessage, e.msg as InquiryMessage);
            CallMessageHandler(OnMoveMessage, e.msg as MoveMessage);
            CallMessageHandler(OnGameMessage, e.msg as GameMessage);
        }

        private void StartNewGameButton_Click(object sender, RoutedEventArgs e)
        {
            NewGame(Guid.NewGuid());
            RestartPanel.Visibility = Visibility.Collapsed;
        }

        private void CloseButton_Click(object sender, RoutedEventArgs e)
        {
            RestartPanel.Visibility = Visibility.Collapsed;
            lastMove = DateTime.Now;
        }

        void timer_Timeout()
        {
            if (CurrentGame().State != GameState.Idling
                && DateTime.Now.Subtract(lastMove) > inactivityTimeout
                && RestartPanel.Visibility != Visibility.Visible)
            {
                Debug.WriteLine("Showing option to start new game due to inactivity");
                RestartPanel.Visibility = Visibility.Visible;
                RestartGameStoryboard.Begin();
            }
        }

        // game logic

        private GameBoard NewGame(Guid gameId)
        {
            EndCurrentGame();

            Debug.WriteLine("Creating new game with id " + gameId);
            RestartPanel.Visibility = Visibility.Collapsed;
            
            var game = new GameBoard(this, gameId);
            GameArea.Children.Add(game);

            return game;
        }

        private GameBoard EndCurrentGame()
        {
            var currentGame = CurrentGame();
            if (currentGame != null)
            {
                currentGame.Visibility = Visibility.Collapsed;
                GameArea.Children.Remove(currentGame);
                
                Debug.WriteLine("Ended game with id " + currentGame.GameId + " games on board: " + GameArea.Children.Count);
            }
            return currentGame;
        }

        private void MoveToHistory(GameBoard lastGame)
        {
            Debug.WriteLine("Moving current game to history");

            if (PreviousGames().Count() == 0)
            {
                HistoryColumn.Width = new GridLength(.3, GridUnitType.Star);
            }
            else if (PreviousGames().Count() >= Cementary.RowDefinitions.Count)
            {
                var oldestGame = PreviousGames().Last();
                Cementary.Children.Remove(oldestGame);
            }

            lastGame.Visibility = Visibility.Visible;
            lastGame.IsEnabled = false;
            Cementary.Children.Insert(0, lastGame);


            foreach (GameBoard previousGame in PreviousGames())
            {
                previousGame.Row(Cementary.Children.IndexOf(previousGame));
            }
        }

        // message logic

        void CallMessageHandler<T>(Action<T> action, T message) where T: DuplexMessage
        {
            if (message != null)
                action(message as T);
        }

        private void OnMoveMessage(MoveMessage move)
        {
            lastMove = DateTime.Now;
            var currentGame = CurrentGame();
            if (move.Move.Host == HostId)
            {
                Debug.WriteLine("Ignoring move from player: " + move.Move.Player + " from host: " + move.Move.Host + " playing game: " + move.GameId);
                return;
            }
            if (move.GameId == currentGame.GameId)
            {
                Debug.WriteLine("Showing move from player: " + move.Move.Player + " from host: " + move.Move.Host + " playing game: " + move.GameId);
                currentGame.ShowMove(move.Move);
            }
            else if (!currentGame.IsPlaying())
            {
                Debug.WriteLine("Showing move on new gameboard from player: " + move.Move.Player + " from host: " + move.Move.Host + " playing game: " + move.GameId);
                currentGame = NewGame(move.GameId);
                currentGame.ShowMove(move.Move);
            }
            else
            {
                Debug.WriteLine("Strange state when message from player: " + move.Move.Player + " from host: " + move.Move.Host + " playing game: " + move.GameId);
            }
        }

        private void OnGameMessage(GameMessage game)
        {
            if (game.HostedBy == HostId)
            {
                Debug.WriteLine("Ignoring response from inquiry: " + game.GameId + " hosted by: " + game.HostedBy + " with moves " + game.Moves.Count);
                return;
            }

            var currentGame = CurrentGame();
            if (game.GameId == currentGame.GameId)
            {
                Debug.WriteLine("Showing game after response from inquiry: " + game.GameId + " hosted by: " + game.HostedBy + " with moves " + game.Moves.Count);
                currentGame.ShowGame(game.Moves);
            }
            else if (!currentGame.IsPlaying())
            {
                Debug.WriteLine("Creating new game after response from inquiry: " + game.GameId + " hosted by: " + game.HostedBy + " with moves " + game.Moves.Count);
                currentGame = NewGame(game.GameId);
                currentGame.ShowGame(game.Moves);
            }
            else
            {
                Debug.WriteLine("Strange state after response from inquiry: " + game.GameId + " hosted by: " + game.HostedBy + " with moves " + game.Moves.Count);
            }

        }

        private void OnInquiryMessage(InquiryMessage inquiry)
        {
            var currentGame = CurrentGame();
            if (inquiry.RequestedBy == HostId)
            {
                Debug.WriteLine("Ignoring inquiry from: " + inquiry.RequestedBy);
                return;
            }
            else if(!currentGame.IsGameMaster)
            {
                Debug.WriteLine("Not master and ignoring inquiry from: " + inquiry.RequestedBy);
                return;
            }

            Debug.WriteLine("Responding to inquiry from: " + inquiry.RequestedBy);
            var game = new GameMessage
            {
                HostedBy = HostId,
                GameId = currentGame.GameId,
                Moves = currentGame.AllMoves().ToList(),
            };
            client.SendToServiceAsync(game);
        }

        #region IGameHost Members

        public string Player { get; set; }
        public Guid HostId { get; set; }

        public void GameBoardMove(Guid gameId, Move move)
        {
            client.SendToServiceAsync(new MoveMessage
            {
                GameId = gameId,
                Move = move
            });
        }

        public void GameBoardEnd(Move winningMove, IList<Move> moves)
        {
            MoveToHistory(EndCurrentGame());
            NewGame(Guid.NewGuid());
        }

        #endregion
    }
}
